/** Integration test harness used to validate the main components of the 
 *  PPRC server are up and operational (not including the client)
 *
 *  @author Scott Monahan
 *  @version $Id$
 *  
 *  File last changed on $Date:$ by $Author:$ in $Rev:$
 *
 */

package info637.pprc.test;

import static info637.pprc.test.TestUtils.*;

import info637.pprc.adaptor.APIConstants;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class AutomatedTestHarness implements ICompleteCallback {
	
	private final static int NUM_OF_TRANSACTIONS = 25;
	private final static int AVG_TRANSACTION_TIME = 3;
	
	private String url = null;
	private List<ProcessingResults> processingResults = null;
	private String sessionId = null;
	private String propertyId = null;
	private String version = null;
	
	public static void main(String[] args) {
		
		AutomatedTestHarness ith = new AutomatedTestHarness(getEnvSelection());
		
		System.out.println("\nEnter Expected Version (e.g. 1.0.0.1):");
		
		Scanner reader = new Scanner(System.in);
		
		System.out.print(">: ");
		
		String version = reader.nextLine();
		
		if (version == null || version.trim().length() == 0) {
			
			System.out.println("Invalid Version. Please Verify The Version Information And Try Again.");
		}
			
		ith.runIntegrationTests(version);
	}
	
	public AutomatedTestHarness(String url) {
		
		this.url = url;
	}
	
	public void runIntegrationTests(String version) {
		
		System.out.println("\nStarting Infrastructure and Performance Testing (" + new Date().toString() + ") ...");
		
		System.out.println("\nPPRC Server URL: " + this.url);
		
		System.out.println("\nVersion: " + version);
		
		System.out.println("\nTest 1: Server Availablity\n");
		
		boolean test1 = testServerAvailablity(version);
		
		System.out.println("\nTest 2: Exposed Endpoints\n");
			
		boolean test2 = testAllExposedEndpoints();
				
		System.out.println("\nTest 3: Performance\n");
				
		boolean test3 = testPerformance();
		
		String outputMsg = "Failed";
		
		if (test1 && test2 && test3) {
			
			outputMsg = "Passed";
		}
					
		System.out.println("\nInfrastructure and Performance Testing Complete (" + new Date().toString() + "): [" + outputMsg + "]");
	}
	
	private boolean testServerAvailablity(String expectedVersion) {
		
		boolean testRes = runTransaction(PUBLIC_PING_URL);
		
		if (testRes || this.version == null || !expectedVersion.equalsIgnoreCase(this.version)) {
			
			System.out.println(">>>>> Test Failed - Expecting Version: [" + expectedVersion + "] Actual: [" + this.version + "]");
			
			return false;
			
		} else {
			
			System.out.println(">>>>> Test Passed - PPRC Version = [" + this.version + "]");
			
			return true;
		}
	}
	
	private boolean testAllExposedEndpoints() {
		
		boolean test1Res = runEndpointTest(ADMIN_LOGIN_URL);
		boolean test2Res =runEndpointTest(ADMIN_ADD_PROPERTY_URL);
		boolean test3Res =runEndpointTest(ADMIN_GET_PROPERTY_LISTINGS_URL);
		boolean test4Res =runEndpointTest(ADMIN_UPDATE_PROPERTY_URL);
		boolean test5Res =runEndpointTest(ADMIN_GET_PROPERTY_DETAIL_URL);
		boolean test6Res =runEndpointTest(PUBLIC_GET_PROPERTY_LISTINGS_URL);
		boolean test7Res =runEndpointTest(PUBLIC_GET_PROPERTY_DETAIL_URL);
		
		if (test1Res && test2Res && test3Res && test4Res && test5Res && test6Res && test7Res) {
			
			return true;
			
		} else {
			
			return false;
		}
	}
	
	private boolean runEndpointTest(String url) {
		
		if (runTransaction(url)) {
			
			System.out.println(">>>>> Test Failed - [" + url + "]");
			
			return false;
			
		} else {
			
			System.out.println(">>>>> Test Passed - [" + url + "]");
			
			return true;
		}
	}
	
	private boolean testPerformance() {
		
		processingResults = new ArrayList<ProcessingResults>();
		
		for (int i = 0; i < NUM_OF_TRANSACTIONS; i++) {
			
			TransactionRunner worker = new TransactionRunner
				(this, this.url, PUBLIC_GET_PROPERTY_DETAIL_URL, 
						buildTransactionParams(PUBLIC_GET_PROPERTY_DETAIL_URL));
			
			worker.start();
		}
		
		while (processingResults.size() != NUM_OF_TRANSACTIONS) {
			
			try {
				
				Thread.sleep(1000);
				
			} catch (Exception ex) {
				
				;
			}
		}
		
		int numberOfErrors = 0;
		Long totalTransactionTime = 0L;
		Long highTransactionTime = 0L;
		Long lowTransactionTime = 0L;
		
		for (ProcessingResults result : processingResults) {
			
			if (result.isError()) {
				
				numberOfErrors++;
			}
			
			totalTransactionTime = totalTransactionTime + result.getTransactionTime();
			
			if (highTransactionTime < result.getTransactionTime()) {
				
				highTransactionTime = result.getTransactionTime();
			}
			
			if (lowTransactionTime == 0 || lowTransactionTime > result.getTransactionTime()) {
				
				lowTransactionTime = result.getTransactionTime();
			}
		}
		
		System.out.println("Results:");
		System.out.println("  Number of transactions run in parallel:      " + NUM_OF_TRANSACTIONS);
		System.out.println("  Number of errors:                            " + numberOfErrors);
		System.out.println("  Low transaction time (in milliseconds):      " + lowTransactionTime);
		System.out.println("  High transaction time (in milliseconds):     " + highTransactionTime);
		System.out.println("  Average transaction time (in milliseconds):  " + totalTransactionTime/NUM_OF_TRANSACTIONS + "\n");
		
		if ((highTransactionTime/1000) > AVG_TRANSACTION_TIME || numberOfErrors != 0) {
			
			System.out.println(">>>>> Test Failed");
			
			return false;
			
		} else {
			
			System.out.println(">>>>> Test Passed");
			
			return true;
		}
	}

	@Override
	public void done(Long transactionTime, boolean isError, String sessionId, String propertyId, String version) {
		
		synchronized(this.processingResults) {
			
			processingResults.add(new ProcessingResults(transactionTime, isError, sessionId, propertyId, version));
		}
	}
	
	private boolean runTransaction(String url) {
		
		processingResults = new ArrayList<ProcessingResults>();
		
		TransactionRunner pingTest = new TransactionRunner
			(this, this.url, url, buildTransactionParams(url));
		
		pingTest.run();
		
		while (processingResults.size() != 1) {
			
			try {
				
				Thread.sleep(500);
				
			} catch (Exception ex) {
				
				;
			}
		}
		
		if (this.sessionId == null) {
			
			this.sessionId = processingResults.get(0).getSessionId();
		}
		
		if (this.propertyId == null) {
			
			this.propertyId = processingResults.get(0).getPropertyId();
		}
		
		if (this.version == null) {
			
			this.version = processingResults.get(0).getVersion();
		}
		
		return processingResults.get(0).isError();
	}
	
	private Map<String, String> buildTransactionParams(String endpoint) {
		
		Map<String, String> out = new HashMap<String, String>();
		
		if (endpoint.equalsIgnoreCase(PUBLIC_PING_URL)) {
			
			// no parameters needed
			
		} else if (endpoint.equalsIgnoreCase(ADMIN_LOGIN_URL)) {
			
			out.put(APIConstants.userName, "adminUser1");
			out.put(APIConstants.password, "adminPassword1");
			
		} else if (endpoint.equalsIgnoreCase(ADMIN_GET_PROPERTY_LISTINGS_URL)) {
			
			out.put(APIConstants.sessionId, this.sessionId);
			out.put(APIConstants.propertyName, "");
			out.put(APIConstants.tenantName, "");
			out.put(APIConstants.balance, "");
			
		} else if (endpoint.equalsIgnoreCase(ADMIN_ADD_PROPERTY_URL)) {
			
			out.put(APIConstants.sessionId, this.sessionId);
			out.put(APIConstants.propertyName, "Integration Test Prop Name" + System.currentTimeMillis());
			out.put(APIConstants.address, "Integration Test Prop Address" + System.currentTimeMillis());
			out.put(APIConstants.city, "Integration Test Prop City" + System.currentTimeMillis());
			out.put(APIConstants.state, "Integration Test Prop State" + System.currentTimeMillis());
			out.put(APIConstants.zip, "Integration Test Prop Zip" + System.currentTimeMillis());
			out.put(APIConstants.monthlyRent, "150000");
			out.put(APIConstants.contactInfo, "John Smith");
			out.put(APIConstants.securityDeposit, "1500000");
			out.put(APIConstants.ownerName, "John Smith");
			out.put(APIConstants.numOfBeds, "4");
			out.put(APIConstants.numOfBaths, "2");
			out.put(APIConstants.sqft, "2000");
			out.put(APIConstants.appliances, "Washer|Dryer|Dish Washer");
			out.put(APIConstants.description, "Some house");
			out.put(APIConstants.showOnline, "0");
			out.put(APIConstants.showInFlyer, "0");
			
		} else if (endpoint.equalsIgnoreCase(ADMIN_GET_PROPERTY_DETAIL_URL)) {
			
			out.put(APIConstants.sessionId, this.sessionId);
			out.put(APIConstants.propertyId, this.propertyId);
			
		} else if (endpoint.equalsIgnoreCase(ADMIN_UPDATE_PROPERTY_URL)) {
			
			out.put(APIConstants.sessionId, sessionId);
			out.put(APIConstants.propertyId, propertyId);
			out.put(APIConstants.propertyName, "");
			out.put(APIConstants.showOnline, "");
			out.put(APIConstants.monthlyRent, "");
			out.put(APIConstants.contactInfo, "");
			out.put(APIConstants.securityDeposit, "");
			out.put(APIConstants.ownerName, "");
			out.put(APIConstants.numOfBeds, "");
			out.put(APIConstants.numOfBaths, "");
			out.put(APIConstants.sqft, "");
			out.put(APIConstants.appliances, "");
			out.put(APIConstants.description, "");
			out.put(APIConstants.showInFlyer, "");
			
		} else if (endpoint.equalsIgnoreCase(PUBLIC_GET_PROPERTY_LISTINGS_URL)) {
			
			out.put(APIConstants.address, "");
			out.put(APIConstants.city, "");
			out.put(APIConstants.state, "");
			out.put(APIConstants.zip, "");
			out.put(APIConstants.monthlyRent, "");
			
		} else if (endpoint.equalsIgnoreCase(PUBLIC_GET_PROPERTY_DETAIL_URL)) {
			
			out.put(APIConstants.propertyId, this.propertyId);
		}
		
		return out;
	}
	
	private class ProcessingResults {
		
		private Long transactionTime = 0L;
		private boolean isError = false;
		private String sessionId = null;
		private String propertyId = null;
		private String version = null;
		
		public ProcessingResults(Long transactionTime, boolean isError, String sessionId, 
			String propertyId, String version) {
			
			this.transactionTime = transactionTime;
			this.isError = isError;
			this.sessionId = sessionId;
			this.propertyId = propertyId;
			this.version = version;
		}
	
		public Long getTransactionTime() {
			
			return transactionTime;
		}
		
		public boolean isError() {
			
			return isError;
		}
		
		public String getSessionId() {
			
			return sessionId;
		}
		
		public String getPropertyId() {
			
			return propertyId;
		}
		
		public String getVersion() {
			
			return version;
		}
	}
}
